package sf.mybatis;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ConfigurationUtils;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import sf.database.template.CType;
import sf.database.template.mybatis.MybatisTemplate;
import sf.tools.codec.HashUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;

public class MybatisSqlTemplate {

    /**
     * @param sqlSession
     * @param beanClass
     * @param useOnce    stament 是否只使用一次
     * @param sqlOrId
     * @param ctype
     * @param parameter  必须是Map类型参数
     * @param <T>
     * @return
     */
    public static <T> T selectOne(SqlSession sqlSession, Class<T> beanClass, boolean useOnce, String sqlOrId, CType ctype, Object parameter) {
        Configuration configuration = sqlSession.getConfiguration();
        String stamentId = dealConfig(configuration, SqlCommandType.SELECT, beanClass, sqlOrId, ctype, parameter);
        Map<String, MappedStatement> mappedStatementMap = ConfigurationUtils.getmappedStatements(configuration);
        try {
            return sqlSession.selectOne(stamentId, parameter);
        } finally {
            if (useOnce) {
                mappedStatementMap.remove(stamentId);
            }
        }
    }

    public static <T> List<T> selectList(SqlSession sqlSession, Class<T> beanClass, boolean useOnce, String sqlOrId, CType ctype) {
        return selectList(sqlSession, beanClass, useOnce, sqlOrId, ctype, null);
    }

    public static <T> List<T> selectList(SqlSession sqlSession, Class<T> beanClass, boolean useOnce, String sqlOrId, CType ctype, Object parameter) {
        return selectList(sqlSession, beanClass, useOnce, sqlOrId, ctype, parameter, RowBounds.DEFAULT);
    }

    public static <T> List<T> selectList(SqlSession sqlSession, Class<T> beanClass, boolean useOnce, String sqlOrId, CType ctype, Object parameter, RowBounds rowBounds) {
        Configuration configuration = sqlSession.getConfiguration();
        String stamentId = dealConfig(configuration, SqlCommandType.SELECT, beanClass, sqlOrId, ctype, parameter);
        Map<String, MappedStatement> mappedStatementMap = ConfigurationUtils.getmappedStatements(configuration);
        try {
            return sqlSession.selectList(stamentId, parameter, rowBounds);
        } finally {
            if (useOnce) {
                mappedStatementMap.remove(stamentId);
            }
        }
    }


    /**
     * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
     * @param sqlSession
     * @return Cursor of mapped objects
     * @param beanClass
     * @param useOnce
     * @param sqlOrId
     * @param ctype
     * @param <T>       the returned cursor element type.
     */
    <T> Cursor<T> selectCursor(SqlSession sqlSession, Class<T> beanClass, boolean useOnce, String sqlOrId, CType ctype){
        return selectCursor(sqlSession,beanClass,useOnce,sqlOrId,ctype,null);
    }

    /**
     * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
     * @param sqlSession
     * @param beanClass
     * @param useOnce
     * @param sqlOrId
     * @param ctype
     * @param parameter A parameter object to pass to the statement.
     * @param <T>       the returned cursor element type.
     * @return Cursor of mapped objects
     */
    public  static  <T> Cursor<T> selectCursor(SqlSession sqlSession, Class<T> beanClass, boolean useOnce, String sqlOrId, CType ctype, Object parameter){
        return selectCursor(sqlSession,beanClass,useOnce,sqlOrId,ctype,parameter,RowBounds.DEFAULT);
    }

    /**
     * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
     * @param sqlSession
     * @param beanClass
     * @param useOnce
     * @param sqlOrId
     * @param ctype
     * @param parameter A parameter object to pass to the statement.
     * @param rowBounds Bounds to limit object retrieval
     * @param <T>       the returned cursor element type.
     * @return Cursor of mapped objects
     */
  public  static <T> Cursor<T> selectCursor(SqlSession sqlSession, Class<T> beanClass, boolean useOnce, String sqlOrId, CType ctype, Object parameter, RowBounds rowBounds) {
        Configuration configuration = sqlSession.getConfiguration();
        String stamentId = dealConfig(configuration, SqlCommandType.SELECT, beanClass, sqlOrId, ctype, parameter);
        Map<String, MappedStatement> mappedStatementMap = ConfigurationUtils.getmappedStatements(configuration);
        try {
            return sqlSession.selectCursor(stamentId, parameter, rowBounds);
        } finally {
            if (useOnce) {
                mappedStatementMap.remove(stamentId);
            }
        }
    }

    public static <T> String dealConfig(Configuration configuration, SqlCommandType sqlCommandType, Class<T> beanClass,
                                        String sqlOrId, CType ctype, Object parameter) {
        String resultMapName = beanClass.getName();
        if (!configuration.hasResultMap(resultMapName)) {
            MybatisTemplate.addResultMap(configuration, beanClass);
        }
        String stamentId = null;
        String sqlTextSource = null;
        switch (ctype) {
            case id:
                stamentId = sqlOrId;
                break;
            case source:
                stamentId = HashUtils.md5(sqlOrId);
                sqlTextSource = sqlOrId;
                break;
            default:
                break;
        }
        Class<?> paramClass = null;
        if (parameter != null) {
            paramClass = parameter.getClass();
        }
        SqlSource sqlSource = configuration.getLanguageDriver(null)
                .createSqlSource(configuration, sqlTextSource, paramClass);
        MappedStatement mappedStatement = new MappedStatement.Builder(configuration, stamentId, sqlSource, sqlCommandType)
                .resultMaps(Collections.singletonList(configuration.getResultMap(resultMapName))).build();
        configuration.addMappedStatement(mappedStatement);
        return stamentId;
    }
}
